import _ from 'lodash'
// import {advancedFields, basicFields, containers, customFields} from "./widgetsConfig.js"
import eventBus from "../event-bus"
import {
    addVJsonId,
    deepTrav,
    getComponentDragGroup,
    getComponentName,
    getDefaultVExport,
    insertPrefixEachLine,
    parseJsObject,
    removeVJsonId
} from "../../utils"
import JSON5 from "json5"
import {toRaw} from 'vue'
import localforage from 'localforage'
import {
    getDesignCmp,
    getLayoutLoadHandler,
    getLayoutOutline,
    getLayoutSaveHandler
} from "../../runtime/CmpStore"

export default class Designer {
    constructor(vueInstance, vexport) {
        if (!vexport) {
            this.defaultVExport = _.cloneDeep(getDefaultVExport())
        } else {
            this.defaultVExport = vexport
        }
        this.vueInstance = vueInstance
    }

    getVJson() {
        return toRaw(this.vjson)
    }

    refreshBEM() {
        _.defer(() => {
            eventBus.$emit('bem-refresh', {sender: this})
        })
    }

    defaultVExport = getDefaultVExport()
    vjson = {
        items: [],
        dialogs: [],
    }
    vexport = getDefaultVExport()

    // 选中组件的 id
    selectedId = null

    // 选中的对话框
    selectedDialogId = null

    // 选中组件的 vjson
    selectedWidget = null

    //选中组件名称
    selectedWidgetName = null

    // 选中组件的 parentComponent, parentList, indexOfParentList 三个属性
    selectedOption = {
        parentComponent: undefined, parentList: undefined, indexOfParentList: undefined
    }
    vueInstance = null
    // 多选模式（只能用于复制）
    mulSelect = []
    clipData = undefined

    layoutType = 'PC'

    historyData = {
        index: -1,  //index: 0,
        maxStep: 20, steps: [],
    }

    spaceCurrentIndex = -1
    spaceIdList = []
    spaceInfo = {}

    initDesigner(resetFormJson) {
        this.vjson.items = []
        this.vexport = _.cloneDeep(this.vexport)

        if (!resetFormJson) {
            this.initHistoryData()
        }
    }

    clearDesigner(skipHistoryChange) {
        let emptyWidgetListFlag = (this.vjson.items.length === 0)
        this.vjson.items = []
        this.selectedId = null
        this.selectedWidgetName = null
        this.selectedWidget = {}  //this.selectedWidget = null
        _.extend(this.vexport, this.defaultVExport) //

        if (!!skipHistoryChange) {
            //什么也不做！！
        } else if (!emptyWidgetListFlag) {
            this.emitHistoryChange()
        } else {
            this.saveCurrentHistoryStep()
        }
    }

    getLayoutType() {
        return this.layoutType
    }

    changeLayoutType(newType) {
        this.layoutType = newType
    }

    /**
     * 根据所有组件的 bind.value 属性
     * 生成 vexport 对应的 data 属性 (除 vjson 之外)
     * 比如：
     * items:[
     *  {bind:{value:"{a.f1}"}}
     *  {bind:{value:"{a.f2}"}}
     *  {bind:{value:"$row.a.b"}}
     * ]
     * 生成=>
     * {
     *     a: {
     *         f1: '',
     *         f2: ''
     *     },
     *     dialog_1: {
     *         a: {
     *            b: ''
     *         }
     *     }
     * }
     * @param useFor 用途 reviewOnly/空
     */
    buildVExportData(useFor) {
        const dataRoot = {}
        const targetVjson = toRaw(this.vjson)
        let currentDialogReference = undefined

        const resolverTarget = (vjson) => {
            deepTrav(vjson, (target, markObj) => {
                if (target.xtype) {
                    if (target.xtype === 'dialog') {
                        currentDialogReference = ''
                        if (target.reference) {
                            _.set(dataRoot, vjson.reference + '.isShow', false)
                            _.set(dataRoot, vjson.reference + '.loading', false)
                            _.set(dataRoot, vjson.reference + '.width', false)
                            _.set(dataRoot, vjson.reference + '.title', false)
                            _.set(dataRoot, vjson.reference + '.row', {})
                            currentDialogReference = _.get(dataRoot, vjson.reference + '.row')
                        }
                        markObj.exitCallback = function () {
                            currentDialogReference = undefined
                        }

                    } else if (target.xtype === 'drawer') {
                        currentDialogReference = ''
                        if (target.reference) {
                            _.set(dataRoot, vjson.reference + '.isShow', false)
                            _.set(dataRoot, vjson.reference + '.loading', false)
                            _.set(dataRoot, vjson.reference + '.width', false)
                            _.set(dataRoot, vjson.reference + '.title', false)
                            _.set(dataRoot, vjson.reference + '.row', {})
                            currentDialogReference = _.get(dataRoot, vjson.reference + '.row')
                        }
                        markObj.exitCallback = function () {
                            currentDialogReference = undefined
                        }

                    } else {
                        const designCmp = getDesignCmp(target)
                        if (designCmp && typeof designCmp.buildVExportData === 'function') {
                            designCmp.buildVExportData(target, dataRoot, useFor)
                        }
                    }
                }

                // 遍历 bind:{ xxx } 所有属性
                if (target.xtype && target.bind) {
                    if (useFor === 'reviewOnly') {
                        _.forOwn(target.bind, (v, k) => {
                            if (_.startsWith(v, "{") && _.endsWith(v, "}")) {
                                if (currentDialogReference) {
                                    if (_.startsWith(v, '{$row.') && typeof currentDialogReference === 'object') {
                                        _.set(currentDialogReference, v.substring(6, v.length - 1), target.id)
                                        return
                                    }
                                }

                                _.set(dataRoot, v.substring(1, v.length - 1), target.id)
                            }
                        })

                    } else {
                        _.forOwn(target.bind, (v, k) => {
                            if (_.startsWith(v, "{") && _.endsWith(v, "}")) {
                                if (currentDialogReference) {
                                    if (_.startsWith(v, '{$row.') && typeof currentDialogReference === 'object') {
                                        _.set(currentDialogReference, v.substring(6, v.length - 1), target.id)
                                        return
                                    }
                                }

                                _.set(dataRoot, v.substring(1, v.length - 1), null)
                            }
                        })
                    }
                }
            })
        }

        _.forEach(targetVjson.extendProps, (row) => {
            if (row.name) {
                _.set(dataRoot, row.name, row.value)
            }
        })
        _.forEach(targetVjson.extendDatas, (row) => {
            if (row.name) {
                _.set(dataRoot, row.name, row.value)
            }
        })

        const define = getLayoutOutline(targetVjson)
        if (!define) {
            console.error('没有找到布局定义', targetVjson)
        } else {
            _.forOwn(define, (define, propName) => {
                if (define.isList === true) {
                    _.forEach(targetVjson[propName], item => {
                        resolverTarget(item)
                    })

                } else {
                    resolverTarget(targetVjson[propName])
                }
            })
        }

        return dataRoot
    }

    _getAllReferenceForNamePool(referenceNamePool, vjson) {
        deepTrav(vjson, (obj) => {
            if (typeof obj.xtype === 'string' && typeof obj.reference === 'string' && obj.reference) {
                referenceNamePool[obj.reference] = '1'
            }
        })
    }

    /**
     * 生成 design.js 文件
     */
    exportFile() {
        const vexport = _.cloneDeep(toRaw(this.vexport))
        const vjson = _.cloneDeep(toRaw(this.vjson))

        const buildFuncBody = (referenceNamePool, targetVJson) => {
            /**
             * 提取 vjson 中所有 bind 属性，并生成新的 vexport.computed 中的方法
             */
            deepTrav(targetVJson, (target) => {
                if (typeof target.xtype === 'string') {
                    _.forOwn(target.bind, (funcBody, propName) => {
                        if (propName === 'value') {
                            return
                        }
                        if (typeof funcBody === 'string' && !_.startsWith(funcBody, '{')) {
                            const computedName = chooseReferenceName(referenceNamePool, target, propName)
                            target.bind[propName] = '{' + computedName + '}'
                            if (!vexport.computed) {
                                vexport.computed = {}
                            }
                            vexport.computed[computedName] = markDynamicMethod(funcBody)
                        }
                    })
                }
            });

            /**
             * 提取 vjson 中所有 listeners 属性，并生成新的 vexport.methods 中的方法.
             */
            deepTrav(targetVJson, (target) => {
                if (typeof target.xtype === 'string') {
                    _.forOwn(target.listeners, (funcBody, propName) => {
                        if (typeof funcBody === 'string' && !_.startsWith(funcBody, '{')) {
                            const methodName = chooseReferenceName(referenceNamePool, target, propName)
                            target.listeners[propName] = '{' + methodName + '}'
                            if (!vexport.methods) {
                                vexport.methods = {}
                            }
                            vexport.methods[methodName] = markDynamicMethod(funcBody)
                        }
                    })
                }
            });

            /**
             * 删除 chooseReferenceName 生成的临时名称属性
             */
            deepTrav(targetVJson, (target) => {
                if (typeof target.xtype === 'string') {
                    if (target._tmpReference) {
                        delete target._tmpReference
                    }
                }
            });

            removeVJsonId(targetVJson)
        }

        /**
         * 提取 vjson.listeners 根事件
         */
        _.forOwn(vjson.listeners, (funcBody, propName) => {
            if (typeof funcBody === 'string' && !_.startsWith(funcBody, '{')) {
                const methodName = propName
                vjson.listeners[propName] = '{' + methodName + '}'
                if (!vexport.methods) {
                    vexport.methods = {}
                }
                vexport.methods[methodName] = markDynamicMethod(funcBody)
            }
        })

        const define = getLayoutOutline(vjson)
        if (!define) {
            console.error('没有找到布局定义', vjson)
        } else {
            // 构造变量取名池，防止重名
            const referenceNamePool = {}
            _.forOwn(define, (define, propName) => {
                if (define.isList === true) {
                    _.forEach(vjson[propName], item => {
                        this._getAllReferenceForNamePool(referenceNamePool, item)
                    })

                } else {
                    this._getAllReferenceForNamePool(referenceNamePool, vjson[propName])
                }
            })
            // 用变量池，进入对象中进行分析
            _.forOwn(define, (define, propName) => {
                if (define.isList === true) {
                    _.forEach(vjson[propName], item => {
                        buildFuncBody(referenceNamePool, item)
                    })

                } else {
                    buildFuncBody(referenceNamePool, vjson[propName])
                }
            })
        }

        // vexport.data = _.defaultsDeep({}, vexport.data, this.buildVExportData())
        // vexport.data.vjson = vjson
        /**
         * 根据 data 和 bind.value 构建新的 data 结构
         */
        const vexportData = _.defaultsDeep({}, vexport.data, this.buildVExportData())
        vexportData.vjson = vjson
        vexport.data = vexportDataToFuncBody(vexportData)

        const saveHandle = getLayoutSaveHandler(vjson.layout)
        if (saveHandle) {
            saveHandle(vexport, vjson)
        }

        const code = stringifyDesignJsObject(vexport)
        // console.log('code', code)
        return code
    }

    /**
     * 根据 vjson, 遍历所有的事件和属性
     */
    buildEventMap() {
        const vjson = toRaw(this.vjson)
        const eventData = []

        const chooseDisplayName = (vjson) => {
            const desc = getComponentDesc(vjson.xtype)
            if (!desc || !desc.displayFn) {
                return vjson.xtype + ' #' + vjson.id
            }
            return desc.displayFn(vjson)
        }

        const deepTravEvents = (targetVJson) => {
            /**
             * 提取 vjson 中所有 bind 属性，并生成新的 vexport.computed 中的方法
             */
            deepTrav(targetVJson, (target) => {
                if (typeof target.xtype === 'string' && target.id) {
                    const displayName = chooseDisplayName(target)

                    /**
                     * 提取 vjson 中所有 bind 属性
                     */
                    _.forOwn(target.bind, (funcBody, propName) => {
                        if (propName === 'value') {
                            return
                        }
                        if (typeof funcBody === 'string' && !_.startsWith(funcBody, '{')) {
                            eventData.push({
                                displayName,
                                propName: propName,
                                cmpId: target.id,
                                parentObj: target.bind
                            })
                        }
                    })

                    /**
                     * 提取 vjson 中所有 listeners 属性
                     */
                    _.forOwn(target.listeners, (funcBody, propName) => {
                        if (typeof funcBody === 'string' && !_.startsWith(funcBody, '{')) {
                            eventData.push({
                                displayName,
                                label: '@' + propName,
                                propName: propName,
                                cmpId: target.id,
                                parentObj: target.listeners
                            })
                        }
                    })
                }
            });
        }

        // 根据布局处理内容
        const define = getLayoutOutline(vjson)
        if (!define) {
            console.error('没有找到布局定义', vjson)
        } else {
            _.forOwn(define, (define, propName) => {
                if (define.isList === true) {
                    _.forEach(vjson[propName], item => {
                        deepTravEvents(item)
                    })

                } else {
                    deepTravEvents(vjson[propName])
                }
            })
        }

        _.forOwn(vjson.listeners, (func, name) => {
            const displayName = name
            eventData.push({
                displayName,
                propName: name,
                cmpId: 'ROOT',
                parentObj: vjson.listeners
            })
        })

        return eventData
    }

    /**
     * 从 design.js 文件读取内容
     */
    loadFormFile(fileContent) {
        let vexport

        if (typeof fileContent === 'string') {
            vexport = parseJsObject(fileContent)
            // console.log('hasLoad', vexport)

        } else if (typeof fileContent === 'object') {
            vexport = _.cloneDeep(fileContent)
            // console.log('hasLoad', vexport)

        } else {
            throw Error('错误的 design.js 文件格式')
        }


        if (!vexport || !_.isObject(vexport)) {
            throw Error('错误的 design.js 文件格式')
        }

        // 将 data 函数结果反馈至 data
        vexport['data'] = vexportFuncBodyToObject(vexport)

        // 拿到 vjson
        const vjson = vexport['data'].vjson
        delete vexport['data'].vjson

        /**
         * 拆解JS方法，变成纯字符串表达式
         * @param targetVJson
         */
        const removeFuncBody = (targetVJson) => {
            /**
             * 解析 vjson 中所有 bind 属性，并替换为 vexport.computed 中的方法体
             *
             * bind: {
             *     xxx: '{abc}'
             * }
             * 转换为=>
             * bind: {
             *     xxx: 'console.log("xxx") ...'
             * }
             */
            deepTrav(targetVJson, (obj) => {
                if (typeof obj.xtype === 'string') {
                    _.forOwn(obj.bind, (value, key) => {
                        if (typeof value === 'string' && _.startsWith(value, '{') && _.endsWith(value, '}')) {
                            if (key === 'value') {
                                return
                            }

                            const computedName = value.substring(1, value.length - 1)
                            obj.bind[key] = stringifyMethodBody(vexport.computed[computedName])
                        }
                    })
                }
                // 处理表格列没有 xtype的情况
                if (obj.xtype === 'yvgrid' && Array.isArray(obj.columns)) {
                    _.forEach(obj.columns, (item) => {
                        if (!item.xtype || typeof item.xtype !== "string") {
                            item.xtype = "gridcolumn"
                        }
                    })
                }
                if (obj.xtype === 'aggrid' && Array.isArray(obj.columns)) {
                    _.forEach(obj.columns, (item) => {
                        if (!item.xtype || typeof item.xtype !== "string") {
                            item.xtype = "aggridcolumn"
                        }
                    })
                }
            });

            /**
             * 解析 vjson 中所有 listeners 属性，并替换为 vexport.methods 中的方法体
             *
             * listeners: {
             *     xxx: '{abc}'
             * }
             * 转换为=>
             * listeners: {
             *     xxx: 'console.log("xxx") ...'
             * }
             */
            deepTrav(targetVJson, (obj) => {
                if (typeof obj.xtype === 'string') {
                    _.forOwn(obj.listeners, (value, key) => {
                        if (typeof value === 'string' && _.startsWith(value, '{') && _.endsWith(value, '}')) {
                            const methodName = value.substring(1, value.length - 1)
                            obj.listeners[key] = stringifyMethodBody(vexport.methods[methodName])
                        }
                    })
                }
            });
        }

        /**
         * 拆解布局事件
         */
        _.forOwn(vjson.listeners, (value, key) => {
            if (typeof value === 'string' && _.startsWith(value, '{') && _.endsWith(value, '}')) {
                const methodName = value.substring(1, value.length - 1)
                vjson.listeners[key] = stringifyMethodBody(vexport.methods[methodName])
            }
        })

        // 根据布局处理内容
        const define = getLayoutOutline(vjson)
        if (!define) {
            console.error('没有找到布局定义', vjson)
        } else {
            _.forOwn(define, (define, propName) => {
                if (define.isList === true) {
                    _.forEach(vjson[propName], item => {
                        removeFuncBody(item)
                    })

                } else {
                    removeFuncBody(vjson[propName])
                }
            })
        }

        // 为 vjson 加上 id
        this._addIdByLayout(vjson)

        vexport = JSON5.parse(JSON5.stringify(vexport))

        const loadHandle = getLayoutLoadHandler(vjson.layout)
        if (loadHandle) {
            loadHandle(vexport, vjson)
        }

        this.vjson = vjson
        this.vexport = vexport

        this.emitEvent('form-json-imported', [])  // 通知其他组件
        this.saveCurrentHistoryStep()
        return true
    }

    /**
     * 根据 id 选择组件, 内部根据组件的 selectme.native 选中组件, 再引起 setSelected(vjson,option) 的回调
     */
    selectById(cmpId) {
        if (!cmpId) {
            return
        }
        const cmp = document.getElementById(cmpId)
        if (cmp) {
            const evObj = new Event('selectme', {
                bubbles: false,
                cancelable: false,
                composed: false
            });
            cmp.dispatchEvent(evObj);
        }
    }

    setSelected(vjson, option) {
        _.defer(() => {
            eventBus.$emit('bem-refresh', {sender: this})
        })
        if (!vjson) {
            this.clearSelected()
            return
        }

        this.selectedWidget = vjson
        if (!!vjson.id) {
            this.selectedId = vjson.id
            this.selectedWidgetName = getComponentName(vjson)
            if (option) {
                this.selectedOption = {
                    parentComponent: option.parentComponent,
                    parentList: option.parentList,
                    indexOfParentList: option.indexOfParentList,
                }
            } else {
                this.selectedOption = {
                    parentComponent: undefined, parentList: undefined, indexOfParentList: undefined,
                }
            }
        } else {
            if (vjson && !vjson.xtype && typeof vjson.layout === 'string') {
                this.selectedId = 'layout'
                this.selectedWidgetName = '根布局'
                this.selectedWidget = vjson
            } else {
                this.selectedId = undefined
                this.selectedWidgetName = ''
                this.selectedWidget = vjson
            }
            this.selectedOption = {
                parentComponent: undefined, parentList: undefined, indexOfParentList: undefined,
            }
        }
    }

    clearSelected() {
        this.selectedId = null
        this.selectedWidgetName = null
        this.selectedWidget = {}  //this.selectedWidget = null
        this.selectedOption.parentComponent = undefined
        this.selectedOption.parentList = undefined
        this.selectedOption.indexOfParentList = undefined
        eventBus.$emit('bem-refresh', {sender: this, source: 'dragEnd'})
    }

    copyComponent(vueInstance, vjson) {
        const parentComponent = this.selectedOption.parentComponent
        const parentList = this.selectedOption.parentList
        const indexOfParentList = this.selectedOption.indexOfParentList

        if (!parentList || !parentComponent) {
            //@ts-ignore
            system.msg('无法复制')
            return
        }

        if (parentList[indexOfParentList]?.id !== vjson?.id) {
            //@ts-ignore
            system.msg(`复制的ID不一致: id=${parentList[indexOfParentList].id}, vjson=${vjson.id}`, parentList[indexOfParentList], vjson)
            return
        }

        const target = _.cloneDeep(toRaw(parentList[indexOfParentList]))
        removeVJsonId(target)
        addVJsonId(target)
        this.mulSelect = []

        parentList.splice(indexOfParentList + 1, 0, target)
        vueInstance.$nextTick(() => {
            this.emitHistoryChange()
            let idx = -1
            if (target && target.id) {
                idx = _.findIndex(parentList, (i) => {
                    return i.id === target.id
                })
            }

            // console.log('copyFinish', target.id, idx, target)
            if (idx < 0) {
                this.setSelected(target)
            } else {
                this.setSelected(target, {
                    parentComponent, parentList, indexOfParentList: idx,
                })
            }
        })
    }

    deleteComponent(vueInstance, vjson) {
        const parentComponent = this.selectedOption.parentComponent
        const parentList = this.selectedOption.parentList
        const indexOfParentList = this.selectedOption.indexOfParentList

        if (!parentList || !parentComponent) {
            //@ts-ignore
            system.msg('无法删除')
            return
        }

        if (parentList[indexOfParentList]?.id !== vjson?.id) {
            //@ts-ignore
            system.msg(`删除的ID不一致: id=${parentList[indexOfParentList].id}, vjson=${vjson.id}`, parentList[indexOfParentList], vjson)
            return
        }
        this.mulSelect = []

        if (!!parentList) {
            let nextSelected = null
            if (parentList.length === 1) {
                if (!!parentComponent) {
                    nextSelected = parentComponent
                }
            } else if (parentList.length === (1 + indexOfParentList)) {
                nextSelected = parentList[indexOfParentList - 1]
            } else {
                nextSelected = parentList[indexOfParentList + 1]
            }

            parentList.splice(indexOfParentList, 1)
            vueInstance.$nextTick(() => {

                this.emitHistoryChange()

                let idx = -1
                if (nextSelected && nextSelected.id) {
                    idx = _.findIndex(parentList, (i) => {
                        return i.id === nextSelected.id
                    })
                }

                if (idx < 0) {
                    this.setSelected(nextSelected)
                } else {
                    this.setSelected(nextSelected, {
                        parentComponent, parentList, indexOfParentList: idx,
                    })
                }

            })
        }
    }

    checkWidgetMove(evt) {
        if (evt && evt.relatedRect) {
            eventBus.$emit('drag-related', {evt, rect: evt.relatedRect})
        }
        return true
    }

    dragEnd() {
        eventBus.$emit('drag-end', {})
        eventBus.$emit('bem-refresh', {sender: this, source: 'dragEnd'})
    }

    // getContainerByType(typeName) {
    //     let allWidgets = [...containers, ...basicFields, ...advancedFields, ...customFields]
    //     let foundCon = null
    //     allWidgets.forEach(con => {
    //         if (!!con.category && !!con.type && (con.type === typeName)) {
    //             foundCon = con
    //         }
    //     })
    //
    //     return foundCon
    // }

    copyNewWidget(origin) {
        let newCon
        if (typeof origin.onCreate === 'function') {
            newCon = origin.onCreate('clone')
        }

        if (!newCon) {
            throw "不支持的组件"
        }

        return newCon
    }

    /**
     * 全局发射事件
     * @param evtName
     * @param evtData
     */
    emitEvent(evtName, evtData) {
        eventBus.$emit(evtName, evtData)
    }

    initHistoryData() {
        this.loadFormContentFromStorage()
        this.historyData.index++
        this.historyData.steps[this.historyData.index] = ({
            vjson: _.cloneDeep(this.vjson), vexport: _.cloneDeep(this.vexport)
        })
    }

    emitHistoryChange() {
        if (this.historyData.index === this.historyData.maxStep - 1) {
            this.historyData.steps.shift()
        } else {
            this.historyData.index++
        }

        this.historyData.steps[this.historyData.index] = ({
            vjson: _.cloneDeep(this.vjson), vexport: _.cloneDeep(this.vexport)
        })

        this.saveFormContentToStorage().finally()

        if (this.historyData.index < this.historyData.steps.length - 1) {
            this.historyData.steps = this.historyData.steps.slice(0, this.historyData.index + 1)
        }
    }

    saveCurrentHistoryStep() {
        this.historyData.steps[this.historyData.index] = _.cloneDeep({
            vjson: this.vjson, vexport: this.vexport
        })

        this.refreshBEM()
        this.saveFormContentToStorage().finally()
    }

    undoHistoryStep(vueInstance) {
        if (this.historyData.index !== 0) {
            this.historyData.index--
        }

        this.vjson = _.cloneDeep(this.historyData.steps[this.historyData.index].vjson)
        this.vexport = _.cloneDeep(this.historyData.steps[this.historyData.index].vexport)
        vueInstance.$nextTick(() => {
            eventBus.$emit('bem-refresh', {sender: this, source: 'undo'})
        })
    }

    redoHistoryStep(vueInstance) {
        if (this.historyData.index !== (this.historyData.steps.length - 1)) {
            this.historyData.index++
        }

        this.vjson = _.cloneDeep(this.historyData.steps[this.historyData.index].vjson)
        this.vexport = _.cloneDeep(this.historyData.steps[this.historyData.index].vexport)
        vueInstance.$nextTick(() => {
            eventBus.$emit('bem-refresh', {sender: this, source: 'redo'})
        })
    }

    undoEnabled() {
        return (this.historyData.index > 0) && (this.historyData.steps.length > 0)
    }

    redoEnabled() {
        return this.historyData.index < (this.historyData.steps.length - 1)
    }

    /**
     * 创建空间
     * @param id 模块标识符（一般就是文件绝对路径）
     * @param module 模块信息, 用于保存时提取, 必须包含 name 属性
     * @param content designJS 文件内容
     */
    async createSpace(id, module, content) {
        let idx = _.findIndex(this.spaceIdList, (v) => v === id)
        if (idx >= 0) {
            // 已经存在
            await this.switchSpace(idx)
            return idx
        }

        this.spaceIdList.push(id)
        this.spaceInfo[id] = {
            label: module.name, module: toRaw(module),
        }

        this.spaceCurrentIndex = this.spaceIdList.length - 1
        setTimeout(() => {
            this.loadFormFile(content)
            this.saveSpace().finally()
        }, 500)

        idx = this.spaceCurrentIndex
        return idx
    }

    /**
     * 载入空间
     * @returns {Promise<void>}
     */
    async loadSpace() {
        const res = await localforage.getItem('space') ?? {}
        this.spaceIdList = res.list ?? []
        this.spaceCurrentIndex = res.currentIndex ?? -1
        this.spaceInfo = res.info ?? {}
        // 语言提示模型被更改
        // TsLanguageSupport.instance.markModelChanged()
    }

    /**
     * 保存当前空间状态
     */
    async saveSpace() {
        const ret = {
            list: toRaw(this.spaceIdList), info: toRaw(this.spaceInfo), currentIndex: toRaw(this.spaceCurrentIndex),
        }
        await localforage.setItem('space', ret)
    }

    /**
     * 切换空间
     */
    async switchSpace(idx) {
        this.spaceCurrentIndex = idx
        this.clearSelected()
        // 语言提示模型被更改
        // TsLanguageSupport.instance.markModelChanged()
        await this.loadFormContentFromStorage()
        await this.saveSpace()
    }

    /**
     * 关闭空间
     */
    async closeSpace(idx) {
        if (this.spaceIdList.length === 1) {
            // 只有一个元素，要关闭，就完全清空
            const id = this.spaceIdList[0]
            await localforage.removeItem('space-' + id)
            delete this.spaceInfo[id]

            this.spaceCurrentIndex = -1
            this.spaceInfo = {}
            this.spaceIdList = []
            await this.saveSpace()
            return
        }

        const id = this.spaceIdList[idx]
        const originId = this.spaceIdList[this.spaceCurrentIndex]
        if (!id) {
            //不存在的标签
            return
        }

        await localforage.removeItem('space-' + id)
        delete this.spaceInfo[id]
        this.spaceIdList.splice(idx, 1)

        if (this.spaceCurrentIndex === idx) {
            // 关闭的是当前标签, 就要选好下一个标签是谁
            if (idx >= this.spaceIdList.length - 1) {
                // 关闭的是最后一个标签, 就是最后一个标签
                this.spaceCurrentIndex = this.spaceIdList.length - 1
            }
            await this.loadFormContentFromStorage()
            await this.saveSpace()

        } else {
            // 关闭的不是当前标签，当前的序号要重新改变
            this.spaceCurrentIndex = _.findIndex(this.spaceIdList, (v) => v === originId)
            await this.saveSpace()
        }
    }

    /**
     * 将当前 designer 中的 vexport/vjson 保存至 localstore
     */
    async saveFormContentToStorage() {
        const id = this.spaceIdList[this.spaceCurrentIndex]
        if (!id) {
            system.showErrorDialog('保存失败, 没有找到 list[' + this.spaceCurrentIndex + '].id')
            return Promise.reject('保存失败')
        }

        const vexport = _.cloneDeep(toRaw(this.vexport))
        const vj = _.cloneDeep(toRaw(this.vjson))
        const targetDialog = _.find(this.vjson.dialogs, d => d.id === this.selectedDialogId)
        const selectedDialogReference = targetDialog ? targetDialog.reference : null

        this._removeIdByLayout(vj)
        const res = {
            vexport: vexport, vjson: vj, selectedDialogReference
        }

        // return localforage.setItem('design_backup', res).finally()
        await localforage.setItem('space-' + id, res)

        /**
         * 下面是 localStorage 的写法
         * const vexport = _.cloneDeep(toRaw(this.vexport))
         * const vj = _.cloneDeep(toRaw(this.vjson))
         *
         * this._removeIdByLayout(vj)
         *
         * window.localStorage.setItem('design_vjson__backup', JSON5.stringify(vj))
         * window.localStorage.setItem('design_vexport_backup', JSON5.stringify(vexport))
         */
    }

    async loadFormContentFromStorage() {
        const id = this.spaceIdList[this.spaceCurrentIndex]
        const res = await localforage.getItem('space-' + id)
        // const res = JSON5.parse(resText)
        if (res) {
            const {vjson, vexport, selectedDialogReference} = res
            if (!!vjson && !!vexport) {
                this._addIdByLayout(vjson)
                this.vjson = vjson
                this.vexport = vexport
                if (selectedDialogReference) {
                    const targetDialog = _.find(this.vjson.dialogs, d => d.reference === selectedDialogReference)
                    if (targetDialog) {
                        this.selectedDialogId = targetDialog.id
                    }
                }
            }
        }

        /**
         * 下面是 localStorage 的写法
         * 不要删除
         * const widgetListBackup = window.localStorage.getItem('design_vjson__backup')
         * const vexportBackup = window.localStorage.getItem('design_vexport_backup')
         * if (!!widgetListBackup && !!vexportBackup) {
         *   const vj = JSON5.parse(widgetListBackup)
         *   this._addIdByLayout(vj)
         *   this.vjson = vj
         *   this.vexport = JSON5.parse(vexportBackup)
         * }
         */
    }

    /**
     * 为 vjson 去掉ID ( 不处理 funcBody  )
     */
    _removeIdByLayout(vjson) {
        const resolverTarget = (target) => {
            // 去掉组件 ID
            removeVJsonId(target)
        }

        // 根据布局处理内容
        const define = getLayoutOutline(vjson)
        if (!define) {
            console.error('没有找到布局定义', vjson)
        } else {
            _.forOwn(define, (define, propName) => {
                if (define.isList === true) {
                    _.forEach(vjson[propName], item => {
                        resolverTarget(item)
                    })

                } else {
                    resolverTarget(vjson[propName])
                }
            })
        }
    }

    /**
     * 为 vjson 加上ID ( 不处理 funcBody  )
     */
    _addIdByLayout(vjson) {

        const resolverTarget = (target) => {
            // 为遗漏的组件，加上 xtype
            deepTrav(target, (it) => {
                if (it.xtype === 'tabs') {
                    _.forEach(it.items, (panel) => {
                        if (!panel.xtype) {
                            panel.xtype = 'tabpanel'
                        }
                    })

                } else if (it.xtype === 'yvgrid' || it.xtype === 'aggrid') {
                    _.forEach(it.columns, (panel) => {
                        if (!panel.xtype) {
                            panel.xtype = 'gridcolumn'
                        }
                    })

                } else if (it.xtype === 'split') {
                    _.forEach(it.items, (panel) => {
                        if (!panel.xtype) {
                            panel.xtype = 'splitarea'
                        }
                    })
                }
            })

            // 为组件加上 ID
            addVJsonId(target)
        }

        // 根据布局处理内容
        const define = getLayoutOutline(vjson)
        if (!define) {
            console.error('没有找到布局定义', vjson)
        } else {
            _.forOwn(define, (define, propName) => {
                if (define.isList === true) {
                    _.forEach(vjson[propName], item => {
                        resolverTarget(item)
                    })

                } else {
                    resolverTarget(vjson[propName])
                }
            })
        }

    }

    clipCopy() {
        const clipData = []
        _.forEach(this.mulSelect, item => {
            const vjson = _.cloneDeep(item.vjson)
            removeVJsonId(vjson)
            clipData.push(vjson)
        })

        this.clipData = clipData
        this.mulSelect = []
        system.msg('已复制')
    }

    tryPaste() {
        const msg = []
        if (getComponentDragGroup(this.selectedWidget) === 'fieldGroup') {
            // 如果当前选中的组件是 field
            if (this.selectedOption.parentList && this.selectedOption.indexOfParentList >= 0) {
                let i = 1
                _.forEach(this.clipData, (item) => {
                    // 循环剪切板，将内部 field 组件一个一个粘贴到目标的后面
                    if (getComponentDragGroup(item) === 'fieldGroup') {
                        const vjson = _.cloneDeep(item)
                        addVJsonId(vjson)
                        this.selectedOption.parentList.splice(this.selectedOption.indexOfParentList + i, 0, vjson)
                        i++
                    } else {
                        msg.push('无法粘贴, row 组件无法粘贴至 field区域' + item.xtype)
                    }
                })
            }

        } else if (getComponentDragGroup(this.selectedWidget) === 'rowGroup') {
            // 如果当前选中的组件是 form / toolbar
            if (this.selectedWidget.xtype === 'form' || this.selectedWidget.xtype === 'toolbar') {
                // 将剪切板 field 组件一个一个粘贴到其内部
                _.forEach(this.clipData, (item) => {
                    // 循环剪切板，将内部 field 组件一个一个粘贴到目标的后面
                    if (getComponentDragGroup(item) === 'fieldGroup') {
                        const vjson = _.cloneDeep(item)
                        addVJsonId(vjson)
                        this.selectedWidget.items.push(vjson)
                    }
                })
            }

            // 将剪切板 row 组件一个一个粘贴到其后面
            if (this.selectedOption.parentList && this.selectedOption.indexOfParentList >= 0) {
                let i = 1
                _.forEach(this.clipData, (item) => {
                    // 循环剪切板，将内部 field 组件一个一个粘贴到目标的后面
                    if (getComponentDragGroup(item) === 'rowGroup') {
                        const vjson = _.cloneDeep(item)
                        addVJsonId(vjson)
                        this.selectedOption.parentList.splice(this.selectedOption.indexOfParentList + i, 0, vjson)
                        i++
                    }
                })
            } else {
                msg.push('无法粘贴, 找不到 parent-list.')
            }
        }

        if (msg.length > 0) {
            system.showErrorDialog(msg.join('; \n'))
        }
    }
}

/**
 * 为 target 组件，取一个方法名字
 * 如果有组件名(reference)，就以 "组件名_属性名" 为名称
 * 如果没有名字，就以 xtype+序号+属性名 为名称
 */
function chooseReferenceName(referenceNamePool, target, propName) {
    if (typeof target.reference === 'string' && _.size(target.reference) > 0) {
        // 如果有组件名，就以 "组件名_属性名" 为名称
        return target.reference + "_" + propName
    }
    if (typeof target._tmpReference === 'string' && _.size(target._tmpReference) > 0) {
        // 如果有临时组件名，就以 "组件名_属性名" 为名称
        return target._tmpReference + "_" + propName
    }

    // 没有组件名，就进行顺序生成
    let name
    let i = 0
    while (true) {
        i++
        name = target.xtype + i
        let isDup = false
        if (referenceNamePool[name]) {
            isDup = true
        }

        if (!isDup) {
            // 发现没有重复的名称，就以这个名称为准
            break
        }
    }

    // 将临时名称加入到 target 属性上，后面再统一删除
    target._tmpReference = name
    referenceNamePool[name] = 'T'
    return name + "_" + propName
}

/**
 * 动态生成JS, 千万不要乱动！！动了就回不来了！
 * computed: {
 *     textfield1_disabled: '["S"]return this.f1 === 'd'["E"]',
 * },
 * methods: {
 *     btn1_click: "['S']console.log(\"form=\", this.$data)['E']",
 *     textfield1_change: "['S']console.log(\"1\");\r\nconsole.log('2');\r\nconsole.log('\\t3');\r\n['E']",
 * },
 * 转换为 =>
 * computed: {
 *     textfield1_disabled() {
 *         return this.f1 === 'd'
 *     },
 * },
 * methods: {
 *     btn1_click() {
 *         console.log("form=", this.$data)'
 *     },
 *     textfield1_change() {
 *         console.log("1");
 *         console.log('2');
 *         console.log('\t3');
 *     }
 * },
 */
function stringifyDesignJsObject(vexport) {
    // 改写 vexport
    const vexportCP = _.cloneDeep(vexport)
    // if (vexportCP.data?.vjson?.layoutParams) {
    //     _.forEach(vexportCP.data.vjson.layoutParams, param => {
    //         if (param?.name) {
    //             vexportCP.props[param.name] = {
    //                 required: false,
    //                 // type: [],
    //                 // default: "",
    //             }
    //         }
    //     });
    // }
    let code = 'export default ' + JSON5.stringify(vexportCP, {replacer: null, space: '    ', quote: '\''})
    while (true) {
        const START = ': \'["S"]'
        const END = '["E"]'

        const s = code.indexOf(START)
        if (s < 0) {
            break
        }
        const e = code.indexOf(END)

        const strExpress = code.substring(s + 2, e + 6)
        let targetStr
        eval("targetStr=" + strExpress + ";")

        //@ts-ignore
        targetStr = targetStr.substring(5, targetStr.length - 5)
        code = code.substring(0, s) + "() {\n" + insertPrefixEachLine(targetStr, '            ') + "\n        }" + code.substring(e + 6)
    }

    return code
}

/**
 * 将 design.js 中的 data 变成函数模式
 * data: {
 *     a:1,
 *     b:2,
 *     vjson: {
 *         layout: 'DefaultLayout'
 *         items: [],
 *     }
 * }
 *
 * 变成代码模式:
 * data () {
 *     return {
 *         a: 1,
 *         b: 2,
 *         vjson: {
 *              layout: 'DefaultLayout',
 *              items: [],
 *         }
 *     }
 * }
 */
function vexportDataToFuncBody(vexportData) {
    return markDynamicMethod('// @VExportData-Start\nreturn ' + JSON5.stringify(vexportData, {
        replacer: null,
        space: '    ',
        quote: '\''
    }) + '\n//@VExportData-End\n')
}

/**
 * 将 design.js 中的 data 函数模式变换为 对象
 */
function vexportFuncBodyToObject(vexport) {
    if (typeof vexport.data === 'function') {
        return vexport.data()
    }
    return vexport.data
}

/**
 * 根据字符串构建方法体, 并加上 ["S"] / ["E"]  作为前后缀，准备替换
 */
function markDynamicMethod(funcBody) {
    return '["S"]' + funcBody + '["E"]'
}

/**
 * 从 function 获取到 functionBody 的字符串格式
 * 并且自动去掉前面的 12 个空格
 */
function stringifyMethodBody(func) {
    if (typeof func === 'function') {
        const bodyWithName = func.toString()
        const funcBody = bodyWithName.substring(bodyWithName.indexOf('{') + 1, bodyWithName.lastIndexOf('}'))

        const body = _.join(_.map(funcBody.split('\n'), line => {
            if (line.startsWith('            ')) {
                return line.substring(12)
            }
            return line
        }), '\n')

        return _.trim(body)
    }
    return ''
}